#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <string>

using namespace std;

// Set nonzero on receipt of SIGALRM
static volatile sig_atomic_t gotAlarm = 0;

// Retrieve and display the real time, and (if 'includeTimer' is
// true) the current value and interval for the ITIMER_REAL timer
static void displayTimes(const string msg, bool includeTimer) {
    static struct timeval start;
    static int            callNum = 0; // Number of calls to this function

    // Initialize elapsed time meter
    if (callNum == 0) {
        if (gettimeofday(&start, nullptr) == -1) {
            perror("gettimeofday");
            exit(EXIT_FAILURE);
        }
    }

    struct timeval currentTV;
    // Print header every 20 lines
    if (callNum % 20 == 0) {
        cout << "        Elapsed   Value Interval" << endl;
    }

    if (gettimeofday(&currentTV, nullptr) == -1) {
        perror("gettimeofday");
        exit(EXIT_FAILURE);
    }
    printf("%-7s %6.2f", msg.c_str(),
           currentTV.tv_sec - start.tv_sec +
               (currentTV.tv_usec - start.tv_usec) / 1000000.0);

    struct itimerval itv;
    if (includeTimer) {
        if (getitimer(ITIMER_REAL, &itv) == -1) {
            perror("getitimer");
            exit(EXIT_FAILURE);
        }
        printf("  %6.2f  %6.2f",
               itv.it_value.tv_sec + itv.it_value.tv_usec / 1000000.0,
               itv.it_interval.tv_sec + itv.it_interval.tv_usec / 1000000.0);
    }

    cout << endl;
    callNum++;
}

static void sigAlarmHandler(int sig) { gotAlarm = 1; }

int main(int argc, char* argv[]) {
    if (argc > 1 && string(argv[1]) == "--help") {
        cout << argv[0] << " [secs [usecs [int-secs [int-usecs]]]]" << endl;
        return 1;
    }

    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags   = 0;
    sa.sa_handler = sigAlarmHandler;
    if (sigaction(SIGALRM, &sa, nullptr) == -1) {
        perror("sigaction");
        return 1;
    }

    // Exit after 3 signals, or on first signal if interval is 0
    struct itimerval itv;
    displayTimes("START:", false);

    // Set timer from the command-line arguments
    itv.it_value.tv_sec     = (argc > 1) ? stoi(argv[1]) : 2;
    itv.it_value.tv_usec    = (argc > 2) ? stoi(argv[2]) : 0;
    itv.it_interval.tv_sec  = (argc > 3) ? stoi(argv[3]) : 0;
    itv.it_interval.tv_usec = (argc > 4) ? stoi(argv[4]) : 0;

    // Number of signals to catch before exiting
    int maxSigs =
        (itv.it_interval.tv_sec == 0 && itv.it_interval.tv_usec == 0) ? 1 : 3;

    if (setitimer(ITIMER_REAL, &itv, nullptr) == -1) {
        perror("setitimer");
        return 1;
    }

    auto prevClock = clock();
    int  sigCnt    = 0; // Number of signals so far caught

    for (;;) {
        // Inner loop consumes at least 0.5 seconds CPU time
        while (((clock() - prevClock) * 10 / CLOCKS_PER_SEC) < 5) {
            if (gotAlarm) {
                gotAlarm = 0;
                displayTimes("ALARM:", true);

                sigCnt++;
                if (sigCnt >= maxSigs) {
                    cout << "That's all folks" << endl;
                    return 0;
                }
            }
        }

        prevClock = clock();
        displayTimes("Main:", true);
    }
}
